package state

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"github.com/go-test/deep"

	"github.com/helmfile/helmfile/pkg/environment"
	"github.com/helmfile/helmfile/pkg/filesystem"
)

func boolPtrToString(ptr *bool) string {
	if ptr == nil {
		return "<nil>"
	}
	return fmt.Sprintf("&%t", *ptr)
}

func TestHelmState_executeTemplates(t *testing.T) {
	tests := []struct {
		name  string
		input ReleaseSpec
		want  ReleaseSpec
	}{
		{
			name: "Has template expressions in chart, values, secrets, version, labels",
			input: ReleaseSpec{
				Chart:          "test-charts/{{ .Release.Name }}",
				Version:        "{{ .Release.Name }}-0.1",
				Name:           "test-app",
				Namespace:      "test-namespace-{{ .Release.Name }}",
				ValuesTemplate: []any{"config/{{ .Environment.Name }}/{{ .Release.Name }}/{{ .Release.ChartVersion }}/values.yaml"},
				Secrets:        []any{"config/{{ .Environment.Name }}/{{ .Release.Name }}/{{ .Release.ChartVersion }}/secrets.yaml"},
				Labels:         map[string]string{"id": "{{ .Release.Name }}"},
			},
			want: ReleaseSpec{
				Chart:     "test-charts/test-app",
				Version:   "test-app-0.1",
				Name:      "test-app",
				Namespace: "test-namespace-test-app",
				Values:    []any{"config/test_env/test-app/test-app-0.1/values.yaml"},
				Secrets:   []any{"config/test_env/test-app/test-app-0.1/secrets.yaml"},
				Labels:    map[string]string{"id": "test-app"},
			},
		},
		{
			name: "Has template expressions in name with recursive refs",
			input: ReleaseSpec{
				Chart:     "test-chart",
				Name:      "{{ .Release.Labels.id }}-{{ .Release.Namespace }}",
				Namespace: "dev",
				Labels:    map[string]string{"id": "{{ .Release.Chart }}"},
			},
			want: ReleaseSpec{
				Chart:     "test-chart",
				Name:      "test-chart-dev",
				Namespace: "dev",
				Labels:    map[string]string{"id": "test-chart"},
			},
		},
		{
			name: "Has template expressions in boolean values",
			input: ReleaseSpec{
				Chart:             "test-chart",
				Name:              "app-dev",
				Namespace:         "dev",
				Labels:            map[string]string{"id": "app"},
				InstalledTemplate: func(i string) *string { return &i }(`{{ eq .Release.Labels.id "app" | ternary "true" "false" }}`),
				VerifyTemplate:    func(i string) *string { return &i }(`{{ true }}`),
				Verify:            func(i bool) *bool { return &i }(false),
				WaitTemplate:      func(i string) *string { return &i }(`{{ false }}`),
			},
			want: ReleaseSpec{
				Chart:     "test-chart",
				Name:      "app-dev",
				Namespace: "dev",
				Labels:    map[string]string{"id": "app"},
				Installed: func(i bool) *bool { return &i }(true),
				Verify:    func(i bool) *bool { return &i }(true),
				Wait:      func(i bool) *bool { return &i }(false),
			},
		},
		{
			name: "Has template in set-values",
			input: ReleaseSpec{
				Chart:     "test-charts/chart",
				Name:      "test-app",
				Namespace: "dev",
				Version:   "1.5",
				SetValuesTemplate: []SetValue{
					{Name: "val1", Value: "{{ .Release.Name }}-val1"},
					{Name: "val2", File: "{{ .Release.Name }}.yml"},
					{Name: "val3", Values: []string{"{{ .Release.Name }}-val2", "{{ .Release.Name }}-val3"}},
					{Name: "val4", Value: "{{ .Release.Chart }}-{{ .Release.ChartVersion}}"},
				},
			},
			want: ReleaseSpec{
				Chart:     "test-charts/chart",
				Name:      "test-app",
				Namespace: "dev",
				Version:   "1.5",
				SetValues: []SetValue{
					{Name: "val1", Value: "test-app-val1"},
					{Name: "val2", File: "test-app.yml"},
					{Name: "val3", Values: []string{"test-app-val2", "test-app-val3"}},
					{Name: "val4", Value: "test-charts/chart-1.5"},
				},
			},
		},
		{
			name: "Has template in values (map)",
			input: ReleaseSpec{
				Chart:          "test-charts/chart",
				Verify:         nil,
				Name:           "app",
				Namespace:      "dev",
				ValuesTemplate: []any{map[string]string{"key": "{{ .Release.Name }}-val0"}},
			},
			want: ReleaseSpec{
				Chart:     "test-charts/chart",
				Verify:    nil,
				Name:      "app",
				Namespace: "dev",
				Values:    []any{map[string]any{"key": "app-val0"}},
			},
		},
		{
			name: "Has template expressions in post renderer args",
			input: ReleaseSpec{
				Chart: "test-chart",
				PostRendererArgs: []string{
					"--release",
					"{{ .Release.Name }}",
					"--chart",
					"{{ .Release.Chart }}",
				},
				Name: "test-release",
			},
			want: ReleaseSpec{
				Chart: "test-chart",
				Name:  "test-release",
				PostRendererArgs: []string{
					"--release",
					"test-chart-dev",
					"--chart",
					"test-chart",
				},
			},
		},
		{
			name: "Version is empty but used in templates (render as empty string)",
			input: ReleaseSpec{
				Name:           "test-app",
				Chart:          "test-charts/{{ .Release.Name }}",
				ValuesTemplate: []any{"config/values-{{ .Release.ChartVersion }}.yaml"},
			},
			want: ReleaseSpec{
				Name:   "test-app",
				Chart:  "test-charts/test-app",
				Values: []any{"config/values-.yaml"},
			},
		},
	}

	for i := range tests {
		tt := tests[i]
		t.Run(tt.name, func(t *testing.T) {
			state := &HelmState{
				fs: &filesystem.FileSystem{
					Glob: func(s string) ([]string, error) { return nil, nil }},
				basePath: ".",
				ReleaseSetSpec: ReleaseSetSpec{
					HelmDefaults: HelmSpec{
						KubeContext: "test_context",
					},
					Env:               environment.Environment{Name: "test_env"},
					OverrideNamespace: "test-namespace_",
					Repositories:      nil,
					Releases: []ReleaseSpec{
						tt.input,
					},
				},
				RenderedValues: map[string]any{},
			}

			r, err := state.ExecuteTemplates()
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				t.FailNow()
			}

			actual := r.Releases[0]

			if !reflect.DeepEqual(actual.Name, tt.want.Name) {
				t.Errorf("expected Name %+v, got %+v", tt.want.Name, actual.Name)
			}
			if !reflect.DeepEqual(actual.Chart, tt.want.Chart) {
				t.Errorf("expected Chart %+v, got %+v", tt.want.Chart, actual.Chart)
			}
			if !reflect.DeepEqual(actual.Namespace, tt.want.Namespace) {
				t.Errorf("expected Namespace %+v, got %+v", tt.want.Namespace, actual.Namespace)
			}
			if diff := deep.Equal(actual.Values, tt.want.Values); diff != nil && len(actual.Values) > 0 {
				t.Errorf("Values differs \n%+v", strings.Join(diff, "\n"))
			}
			if diff := deep.Equal(actual.Secrets, tt.want.Secrets); diff != nil && len(actual.Secrets) > 0 {
				t.Errorf("Secrets differs \n%+v", strings.Join(diff, "\n"))
			}
			if diff := deep.Equal(actual.SetValues, tt.want.SetValues); diff != nil && len(actual.SetValues) > 0 {
				t.Errorf("SetValues differs \n%+v", strings.Join(diff, "\n"))
			}
			if diff := deep.Equal(actual.Labels, tt.want.Labels); diff != nil && len(actual.Labels) > 0 {
				t.Errorf("Labels differs \n%+v", strings.Join(diff, "\n"))
			}
			if !reflect.DeepEqual(actual.Version, tt.want.Version) {
				t.Errorf("expected Version %+v, got %+v", tt.want.Version, actual.Version)
			}
			if !reflect.DeepEqual(actual.Installed, tt.want.Installed) {
				t.Errorf("expected actual.Installed %+v, got %+v",
					boolPtrToString(tt.want.Installed), boolPtrToString(actual.Installed),
				)
			}
			if !reflect.DeepEqual(actual.Verify, tt.want.Verify) {
				t.Errorf("expected actual.Verify %+v, got %+v",
					boolPtrToString(tt.want.Verify), boolPtrToString(actual.Verify),
				)
			}
			if !reflect.DeepEqual(actual.Wait, tt.want.Wait) {
				t.Errorf("expected actual.Wait %+v, got %+v",
					boolPtrToString(tt.want.Wait), boolPtrToString(actual.Wait),
				)
			}
		})
	}
}

func TestHelmState_recursiveRefsTemplates(t *testing.T) {
	tests := []struct {
		name  string
		input ReleaseSpec
	}{
		{
			name: "Has reqursive references",
			input: ReleaseSpec{
				Chart:     "test-charts/{{ .Release.Name }}",
				Verify:    nil,
				Name:      "{{ .Release.Labels.id }}",
				Namespace: "dev",
				Labels:    map[string]string{"id": "app-{{ .Release.Name }}"},
			},
		},
		{
			name: "Has unresolvable boolean templates",
			input: ReleaseSpec{
				Name:         "app-dev",
				Chart:        "test-charts/app",
				Verify:       nil,
				Namespace:    "dev",
				WaitTemplate: func(i string) *string { return &i }("hi"),
			},
		},
	}

	for i := range tests {
		tt := tests[i]
		t.Run(tt.name, func(t *testing.T) {
			state := &HelmState{
				basePath: ".",
				fs: &filesystem.FileSystem{
					Glob: func(s string) ([]string, error) { return nil, nil },
				},
				ReleaseSetSpec: ReleaseSetSpec{
					HelmDefaults: HelmSpec{
						KubeContext: "test_context",
					},
					Env:               environment.Environment{Name: "test_env"},
					OverrideNamespace: "test-namespace_",
					Repositories:      nil,
					Releases: []ReleaseSpec{
						tt.input,
					},
				},
				RenderedValues: map[string]any{},
			}

			r, err := state.ExecuteTemplates()
			if err == nil {
				t.Errorf("Expected error, got valid response: %v", r)
				t.FailNow()
			}
		})
	}
}
